/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.sample.hello;

import java.io.InputStream;
import java.security.cert.CertificateException;
import java.util.List;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;

import com.swak.utils.Bytes;
import com.swak.utils.Lists;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.buffer.impl.BufferImpl;
import io.vertx.core.net.impl.KeyStoreHelper;

/**
 * An HTTP/2 Server that responds to requests with a Hello World. Once started, you can test the
 * server with the example client.
 */
public final class Http2Server {

	static final boolean SSL = true;

	static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "443" : "8080"));

	static SslContext createSslContext() throws CertificateException, SSLException {
		SslProvider provider = SslProvider.isAlpnSupported(SslProvider.OPENSSL) ? SslProvider.OPENSSL : SslProvider.JDK;
		SelfSignedCertificate ssc = new SelfSignedCertificate();
		SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).sslProvider(provider)
				/* NOTE: the cipher filter may not include all ciphers required by the HTTP/2 specification.
				 * Please refer to the HTTP/2 specification for cipher requirements. */
				.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
				.applicationProtocolConfig(new ApplicationProtocolConfig(Protocol.ALPN,
						// NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK
						// providers.
						SelectorFailureBehavior.NO_ADVERTISE,
						// ACCEPT is currently the only mode supported by both OpenSsl and JDK
						// providers.
						SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2,
						ApplicationProtocolNames.HTTP_1_1))
				.build();
		return sslCtx;
	}

	static SslContext createMySslContext() throws Exception {
		InputStream keyValueIs = Http2Server.class.getResourceAsStream("/127.swakx.cn-key.pem");
		InputStream certValueIs = Http2Server.class.getResourceAsStream("/127.swakx.cn.pem");

		Buffer keyValue = BufferImpl.buffer(Bytes.bytes(keyValueIs));
		Buffer certValue = BufferImpl.buffer(Bytes.bytes(certValueIs));

		List<Buffer> keys = Lists.newArrayList(keyValue);
		List<Buffer> certs = Lists.newArrayList(certValue);
		KeyStoreHelper helper = new KeyStoreHelper(KeyStoreHelper.loadKeyCert(keys, certs),
				KeyStoreHelper.DUMMY_PASSWORD, null);
		KeyManagerFactory kmf = helper.getKeyMgrFactory();

		SslProvider provider = SslProvider.isAlpnSupported(SslProvider.OPENSSL) ? SslProvider.OPENSSL : SslProvider.JDK;
		SslContext sslCtx = SslContextBuilder.forServer(kmf).sslProvider(provider)
				/* NOTE: the cipher filter may not include all ciphers required by the HTTP/2 specification.
				 * Please refer to the HTTP/2 specification for cipher requirements. */
				.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
				.applicationProtocolConfig(new ApplicationProtocolConfig(Protocol.ALPN,
						// NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK
						// providers.
						SelectorFailureBehavior.NO_ADVERTISE,
						// ACCEPT is currently the only mode supported by both OpenSsl and JDK
						// providers.
						SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2,
						ApplicationProtocolNames.HTTP_1_1))
				.build();
		return sslCtx;
	}

	public static void main(String[] args) throws Exception {
		// Configure SSL.
		final SslContext sslCtx;
		if (SSL) {
			sslCtx = createMySslContext();
		} else {
			sslCtx = null;
		}
		// Configure the server.
		EventLoopGroup group = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.option(ChannelOption.SO_BACKLOG, 1024);
			b.group(group).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
					.childHandler(new Http2ServerInitializer(sslCtx));

			Channel ch = b.bind(PORT).sync().channel();

			System.err.println("Open your HTTP/2-enabled web browser and navigate to " + (SSL ? "https" : "http")
					+ "://127.0.0.1:" + PORT + '/');

			ch.closeFuture().sync();
		} finally {
			group.shutdownGracefully();
		}
	}
}
